package teste;

/*******************************************************************************
 * Copyright (c) 2000, 2011 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     jens.lukowski@gmx.de - contributed code to convert prefix and postfix
 *       expressions into a combination of setter and getter calls.
 *******************************************************************************/


import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.internal.corext.SourceRangeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.sef.SelfEncapsulateFieldRefactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.text.edits.TextEditGroup;

/**
 * Analyzer to find all references to the field and to determine how to convert
 * them into setter or getter calls.
 */
class CopyOfAccessAnalyzer extends ASTVisitor {

    private ICompilationUnit fCUnit;
    private IVariableBinding fFieldBinding;
    private ITypeBinding fDeclaringClassBinding;
    private String fGetter;
    private String fSetter;
    private ASTRewrite fRewriter;
    private ImportRewrite fImportRewriter;
    private List<TextEditGroup> fGroupDescriptions;
    private RefactoringStatus fStatus;
    private boolean fSetterMustReturnValue;
    private boolean fEncapsulateDeclaringClass;
    private boolean fIsFieldFinal;

    private boolean fRemoveStaticImport;
    private boolean fReferencingGetter;
    private boolean fReferencingSetter;

    private static final String READ_ACCESS= RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_encapsulate_read_access;
    private static final String WRITE_ACCESS= RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_encapsulate_write_access;
    private static final String PREFIX_ACCESS= RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_encapsulate_prefix_access;
    private static final String POSTFIX_ACCESS= RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_encapsulate_postfix_access;

    public CopyOfAccessAnalyzer(SelfEncapsulateFieldRefactoring refactoring, ICompilationUnit unit, IVariableBinding field, ITypeBinding declaringClass, ASTRewrite rewriter, ImportRewrite importRewrite) {
        Assert.isNotNull(refactoring);
        Assert.isNotNull(unit);
        Assert.isNotNull(field);
        Assert.isNotNull(declaringClass);
        Assert.isNotNull(rewriter);
        Assert.isNotNull(importRewrite);
        fCUnit= unit;
        fFieldBinding= field.getVariableDeclaration();
        fDeclaringClassBinding= declaringClass;
        fRewriter= rewriter;
        fImportRewriter= importRewrite;
        fGroupDescriptions= new ArrayList<TextEditGroup>();
        fGetter= refactoring.getGetterName();
        fSetter= refactoring.getSetterName();
        fEncapsulateDeclaringClass= refactoring.getEncapsulateDeclaringClass();
        try {
            fIsFieldFinal= Flags.isFinal(refactoring.getField().getFlags());
        } catch (JavaModelException e) {
            // assume non final field
        }
        fStatus= new RefactoringStatus();
    }

    public boolean getSetterMustReturnValue() {
        return fSetterMustReturnValue;
    }

    public RefactoringStatus getStatus() {
        return fStatus;
    }

    public List<TextEditGroup> getGroupDescriptions() {
        return fGroupDescriptions;
    }

    @Override
    public boolean visit(Assignment node) {
        Expression lhs= node.getLeftHandSide();
        if (!considerBinding(resolveBinding(lhs), lhs))
            return true;

        checkParent(node);
        if (!fIsFieldFinal) {
            // Write access.
            AST ast= node.getAST();
            MethodInvocation invocation= ast.newMethodInvocation();
            invocation.setName(ast.newSimpleName(fSetter));
            fReferencingSetter= true;
            Expression receiver= getReceiver(lhs);
            if (receiver != null)
                invocation.setExpression((Expression)fRewriter.createCopyTarget(receiver));
            List<Expression> arguments= invocation.arguments();
            if (node.getOperator() == Assignment.Operator.ASSIGN) {
                arguments.add((Expression)fRewriter.createCopyTarget(node.getRightHandSide()));
            } else {
                // This is the compound assignment case: field+= 10;
                InfixExpression exp= ast.newInfixExpression();
                exp.setOperator(ASTNodes.convertToInfixOperator(node.getOperator()));
                MethodInvocation getter= ast.newMethodInvocation();
                getter.setName(ast.newSimpleName(fGetter));
                fReferencingGetter= true;
                if (receiver != null)
                    getter.setExpression((Expression)fRewriter.createCopyTarget(receiver));
                exp.setLeftOperand(getter);
                Expression rhs= (Expression)fRewriter.createCopyTarget(node.getRightHandSide());
//                if (NecessaryParenthesesChecker.needsParentheses(node.getRightHandSide(), exp, InfixExpression.RIGHT_OPERAND_PROPERTY)) {
//                    //TODO: this introduces extra parentheses as the new 'exp' node doesn't have bindings
//                    ParenthesizedExpression p= ast.newParenthesizedExpression();
//                    p.setExpression(rhs);
//                    rhs= p;
//                }
                exp.setRightOperand(rhs);
                arguments.add(exp);
            }
            fRewriter.replace(node, invocation, createGroupDescription(WRITE_ACCESS));
        }
        node.getRightHandSide().accept(this);
        return false;
    }

    @Override
    public boolean visit(SimpleName node) {
        if (!node.isDeclaration() && considerBinding(node.resolveBinding(), node)) {
            fReferencingGetter= true;
            fRewriter.replace(
                node,
                fRewriter.createStringPlaceholder(fGetter + "()", ASTNode.METHOD_INVOCATION), //$NON-NLS-1$
                createGroupDescription(READ_ACCESS));
        }
        return true;
    }

    @Override
    public boolean visit(ImportDeclaration node) {
        if (considerBinding(node.resolveBinding(), node)) {
            fRemoveStaticImport= true;
        }
        return false;
    }

    @Override
    public boolean visit(PrefixExpression node) {
        Expression operand= node.getOperand();
        if (!considerBinding(resolveBinding(operand), operand))
            return true;

        PrefixExpression.Operator operator= node.getOperator();
        if (operator != PrefixExpression.Operator.INCREMENT && operator != PrefixExpression.Operator.DECREMENT)
            return true;

        checkParent(node);

        fRewriter.replace(node,
            createInvocation(node.getAST(), node.getOperand(), node.getOperator().toString()),
            createGroupDescription(PREFIX_ACCESS));
        return false;
    }

    @Override
    public boolean visit(PostfixExpression node) {
        Expression operand= node.getOperand();
        if (!considerBinding(resolveBinding(operand), operand))
            return true;

        ASTNode parent= node.getParent();
        if (!(parent instanceof ExpressionStatement)) {
            fStatus.addError(RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_cannot_convert_postfix_expression,
                JavaStatusContext.create(fCUnit, SourceRangeFactory.create(node)));
            return false;
        }
        fRewriter.replace(node,
            createInvocation(node.getAST(), node.getOperand(), node.getOperator().toString()),
            createGroupDescription(POSTFIX_ACCESS));
        return false;
    }

    @Override
    public boolean visit(MethodDeclaration node) {
        String name= node.getName().getIdentifier();
        if (name.equals(fGetter) || name.equals(fSetter))
            return false;
        return true;
    }

    @Override
    public void endVisit(CompilationUnit node) {
        // If we don't had a static import to the field we don't
        // have to add any, even if we generated a setter or
        // getter access.
        if (!fRemoveStaticImport)
            return;

        ITypeBinding type= fFieldBinding.getDeclaringClass();
        String fieldName= fFieldBinding.getName();
        String typeName= type.getQualifiedName();
        if (fRemoveStaticImport) {
            fImportRewriter.removeStaticImport(typeName + "." + fieldName); //$NON-NLS-1$
        }
        if (fReferencingGetter) {
            fImportRewriter.addStaticImport(typeName, fGetter, false);
        }
        if (fReferencingSetter) {
            fImportRewriter.addStaticImport(typeName, fSetter, false);
        }
    }

    private boolean considerBinding(IBinding binding, ASTNode node) {
        if (!(binding instanceof IVariableBinding))
            return false;
        boolean result= Bindings.equals(fFieldBinding, ((IVariableBinding)binding).getVariableDeclaration());
        if (!result || fEncapsulateDeclaringClass)
            return result;

        if (binding instanceof IVariableBinding) {
            AbstractTypeDeclaration type= (AbstractTypeDeclaration)ASTNodes.getParent(node, AbstractTypeDeclaration.class);
            if (type != null) {
                ITypeBinding declaringType= type.resolveBinding();
                return !Bindings.equals(fDeclaringClassBinding, declaringType);
            }
        }
        return true;
    }

    private void checkParent(ASTNode node) {
        ASTNode parent= node.getParent();
        if (!(parent instanceof ExpressionStatement))
            fSetterMustReturnValue= true;
    }

    private IBinding resolveBinding(Expression expression) {
        if (expression instanceof SimpleName)
            return ((SimpleName)expression).resolveBinding();
        else if (expression instanceof QualifiedName)
            return ((QualifiedName)expression).resolveBinding();
        else if (expression instanceof FieldAccess)
            return ((FieldAccess)expression).getName().resolveBinding();
        return null;
    }

    private Expression getReceiver(Expression expression) {
        int type= expression.getNodeType();
        switch(type) {
            case ASTNode.SIMPLE_NAME:
                return null;
            case ASTNode.QUALIFIED_NAME:
                return ((QualifiedName)expression).getQualifier();
            case ASTNode.FIELD_ACCESS:
                return ((FieldAccess)expression).getExpression();
        }
        return null;
    }

    private MethodInvocation createInvocation(AST ast, Expression operand, String operator) {
        Expression receiver= getReceiver(operand);
        MethodInvocation invocation= ast.newMethodInvocation();
        invocation.setName(ast.newSimpleName(fSetter));
        if (receiver != null)
            invocation.setExpression((Expression)fRewriter.createCopyTarget(receiver));
        InfixExpression argument= ast.newInfixExpression();
        invocation.arguments().add(argument);
        if ("++".equals(operator)) { //$NON-NLS-1$
            argument.setOperator(InfixExpression.Operator.PLUS);
        } else if ("--".equals(operator)) { //$NON-NLS-1$
            argument.setOperator(InfixExpression.Operator.MINUS);
        } else {
            Assert.isTrue(false, "Should not happen"); //$NON-NLS-1$
        }
        MethodInvocation getter= ast.newMethodInvocation();
        getter.setName(ast.newSimpleName(fGetter));
        if (receiver != null)
            getter.setExpression((Expression)fRewriter.createCopyTarget(receiver));
        argument.setLeftOperand(getter);
        argument.setRightOperand(ast.newNumberLiteral("1")); //$NON-NLS-1$

        fReferencingGetter= true;
        fReferencingSetter= true;

        return invocation;
    }

    private TextEditGroup createGroupDescription(String name) {
        TextEditGroup result= new TextEditGroup(name);
        fGroupDescriptions.add(result);
        return result;
    }
}
